// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using Byond.Compilation;
using Byond.HostObjects;
using Newtonsoft.Json;

namespace Byond.Persistence
{
	public sealed class PersistenceManager
	{
		private readonly PatchCompilationInfo _patchCompilationInfo;
		private readonly RuntimeScope _runtimeScope;
		private readonly Env _env;
		private readonly JsonSerializerSettings _jsonSerializerSettings;

		// TODO: HACK: Gegenseitige Referenzierung von UiEnsRunner und PersMgr
		private readonly Lazy<IEnsembleRunner> _ensembleRunner;

		public PersistenceManager()
		{
			_jsonSerializerSettings = new JsonSerializerSettings
				{
					TypeNameHandling = TypeNameHandling.All,
					Error = (s1, e1) =>
						{
							// TODO: unten beim Deserialisieren muss der Fehler verwertet werden, da sonst 'null' beim dto rauskommt.
							e1.ErrorContext.Handled = true;
						}
				};
		}

		public bool ShouldPersistCurrentState
		{
			get { return _runtimeScope.Current.PatchManager.RootPatch != Patch.Empty; }
		}

		public string PersistCurrentStateToJson()
		{
			var dto = new EnsembleDto
				{
					EnsembleFileName = _runtimeScope.Current.PatchManager.RootEnsemble.FileName,
					RelativeEnsembleFileName = _runtimeScope.Current.PatchManager.RootEnsemble.FileName.SubtractLeftPath(_env.PatchPath),
					WasRunning = _runtimeScope.Current.PatchManager.RootPatch != Patch.Empty,
					Patches = _runtimeScope.Current.PatchManager.Patches
						.Select(it => it.ToDto())
						.ToList().Select(it => JsonConvert.SerializeObject(it, _jsonSerializerSettings)).ToArray()
				};

			var json = JsonConvert.SerializeObject(dto, _jsonSerializerSettings);
			return json;
		}

		public Lazy<IEnumerable<PatchDto>> PersistCurrentState()
		{
			var json = PersistCurrentStateToJson();

			// HACK: Ganz generell: Die DTOs reichen nicht aus, da ich innerhalb
			// eines Patches mit [Persist] dynamisch kompilierte Patch-Typen auch
			// persistieren kann. Deshalb muss hier (wegen der unterschiedlichen Typen)
			// ...

			var result = Deserialize(json);
			return result.Item2;
		}

		public void RestoreAndRunEnsemble(string json)
		{
			var result = Deserialize(json);

			if (result.Item1.WasRunning)
			{
				var ensembleFileName = string.IsNullOrEmpty(result.Item1.RelativeEnsembleFileName)
					? result.Item1.EnsembleFileName
					: (_env.PatchPath + result.Item1.RelativeEnsembleFileName);
				_ensembleRunner.Value.Run(ensembleFileName, result.Item2);
			}
		}

		private Tuple<EnsembleDto, Lazy<IEnumerable<PatchDto>>> Deserialize(string json)
		{
			var dto = JsonConvert.DeserializeObject<EnsembleDto>(json, _jsonSerializerSettings);

			// TODO: Das ganze Persistence Zeugs wieder rein machen
			var pendingRestoreInfo = new Lazy<IEnumerable<PatchDto>>(
				() => dto.Patches
					.Select(it => JsonConvert.DeserializeObject<PatchDto>(
						ReplaceAssemblyName(it), _jsonSerializerSettings))
					.ToList());

			return new Tuple<EnsembleDto, Lazy<IEnumerable<PatchDto>>>(dto, pendingRestoreInfo);
		}

		private string ReplaceAssemblyName(string json)
		{
			// HACK!!!
			var lastIndex = 0;
			while (true)
			{
				var index = json.IndexOf(PatchCompilationInfo.DynamicAssemblyCountingName, lastIndex);
				if (index == -1)
					return json;

				json = json.Remove(
					index,
					PatchCompilationInfo.DynamicAssemblyCountingName.Length + PatchCompilationInfo.CountingDigits);
				json = json.Insert(
					index,
					PatchCompilationInfo.DynamicAssemblyCountingName + _patchCompilationInfo.CountingSuffix);

				lastIndex = index + 1;
			}
		}
	}
}